package com.acceptable.qucun.generic.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.*;

public class CodeUtil {
    // 加密: URLEncoder ==> base64加密
    // Base64 加密
    public static <dataMap> Map<String, String> base64Encode(Map<String, ? extends Object> dataMap){
        Map<String, String> code = new HashMap<>();
        if(dataMap == null)
            return code;
        Iterator<? extends Map.Entry<String, ?>> entryIterator = dataMap.entrySet().iterator();
        Base64.Encoder encoder = Base64.getEncoder();
        while(entryIterator.hasNext()){
            Map.Entry<String, ?> entry = entryIterator.next();
            String key = entry.getKey();
            Object tmp = entry.getValue();
            Object value = tmp;
            if(tmp != null){
                // 先转String
                tmp = object2String(value);
                // 再加密
                if (key.contains("file_data")){
                    value = new String((byte[]) value);
                }else if(tmp.getClass().getSimpleName().contains("String")){
                    value = new String(encoder.encode(URLEncoder.encode((String) tmp).getBytes()));
                }
            }
            code.put(key, (String) value);
        }
        return code;
    }

    public static String base64Encode(String data){
        return StringFormatUtil.isEmpty(data) ? "" :
                new String(Base64.getEncoder().encode(URLEncoder.encode(data).getBytes()));
    }

    // 解密:
    // Base64 解密 base64解密 ==> URLDecoder
    public static <dataMap> Map<String, String> base64Decode(Map<String, ? extends Object> codeMap){
        Map<String, String> data = new HashMap<>();
        Iterator<? extends Map.Entry<String, ?>> entryIterator = codeMap.entrySet().iterator();
        Base64.Decoder decoder = Base64.getDecoder();
        while(entryIterator.hasNext()){
            Map.Entry<String, ?> entry = entryIterator.next();
            String key = entry.getKey();
            Object value = entry.getValue();
            if(!key.startsWith("raw_"))
                value = URLDecoder.decode(new String(decoder.decode((String) value)));
            data.put(key, (String) value);
        }
        return data;
    }

    // application/json 格式数据解密 Base64
    public static Map<String, String> jsonBase64Decode(ServletInputStream in){
        String dataStr = StringFormatUtil.createFromServletInputStream(in);
        if("".equals(dataStr))
            return null;
        return base64Decode((Map<String, String>) string2Object(dataStr));
    }

    // application/x-www-form-urlencoded 格式数据读取并解密 Base64
    public static Map<String, String> urlencodedBase64Decode(ServletInputStream in){
        String dataStr = StringFormatUtil.createFromServletInputStream(in);
        Map<String, String> codeMap = new HashMap<>();
        String[] itemList =  dataStr.split("&");
        for (String item: itemList) {
            if(item.trim().length() == 0)
                continue;
            String key = item.substring(0, item.indexOf("="));
            String value = item.substring(item.indexOf("=") + 1);
            codeMap.put(key, value);
        }
        return base64Decode(codeMap);
    }

    // multipart/form-data 格式数据读取并解密 Base64
    public static Map<String, String> formDataBase64Decode(HttpServletRequest request)
            throws IOException, ServletException {
        Map<String, Object> codeMap = new HashMap<>();
        for (Part part: request.getParts()){
            String key = part.getName();
            Object value;
            byte[] arr = new byte[(int) part.getSize()];
            int flag = part.getInputStream().read(arr);
            value = new String(arr).trim();
            codeMap.put(key, value);
        }
        return base64Decode(codeMap);
    }

    public static String base64Decode(String code){
        return StringFormatUtil.isEmpty(code) ? "" :
                URLDecoder.decode(new String(Base64.getDecoder().decode(code)));
    }

    // Object转String
    public static String object2String(Object obj){
        String data = "";
        try {
            if(obj != null)
                data = new ObjectMapper().writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return data;
    }

    // String转Object
    public static Object string2Object(String data){
        Object obj = null;
        try {
            if(!StringFormatUtil.isEmpty(data))
                obj = new ObjectMapper().readValue(data, new TypeReference<Object>() {});
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return obj;
    }

    // Object 转 cookie值
    public static String secretCookieValue(Object object){
        return base64Encode(URLEncoder.encode(object2String(object)));
    }

    public static String normalCookieValue(Object object){
        return URLEncoder.encode(object2String(object));
    }

    public static <T> List<T> string2List(String str, Class<T> clz){
        str = str.replace("[", "").replace("]", "");

        String[] dataStrList = str.split(",");
        List<T> eleList = new ArrayList<>();
        for (String e: dataStrList){
            eleList.add((T) string2Object(e));
        }
        return eleList;
    }

    public static  <T> T map2Object(Map<String, Object> map, Class<T> clz){
        T obj = null;
        try {
            obj = clz.newInstance();
            for (Map.Entry<String, Object> entry: map.entrySet()){
                String name = entry.getKey();
                Object value = entry.getValue();
                if (value == null)
                    continue;
                Field field = clz.getDeclaredField(name);
                field.setAccessible(true);
                field.set(obj, value);
            }
        } catch (ReflectiveOperationException e) {
            e.printStackTrace();
        }
        return obj;
    }
}
